//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.server;

import java.sql.SQLException;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

import org.eclipse.jetty.websocket.WebSocket;

import tv.dyndns.kishibe.client.constant.Constant;
import tv.dyndns.kishibe.client.packet.PacketChatData;
import tv.dyndns.kishibe.client.packet.PacketChatDataList;
import tv.dyndns.kishibe.server.database.Database;
import tv.dyndns.kishibe.server.websocket.BroadcastWebSockets;

import com.google.gson.Gson;
import com.google.inject.Inject;

public class ChatManager {
	private volatile NavigableMap<Integer, PacketChatData> data;
	private final Object writeLock = new Object();
	private final BroadcastWebSockets<PacketChatDataList> chatDataListWebSockets = new BroadcastWebSockets<PacketChatDataList>() {
		@Override
		protected String parse(PacketChatDataList data) {
			return new Gson().toJson(data);
		}
	};
	private final Database database;

	@Inject
	public ChatManager(Database database) {
		this.database = database;
	}

	private void initializeIfNotInitialized() {
		// 二重チェックによる遅延読み込み
		if (data == null) {
			synchronized (this) {
				if (data == null) {
					data = new ConcurrentSkipListMap<Integer, PacketChatData>();

					try {
						data.putAll(database.getLatestChatData());
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	/**
	 * 発言を追加する
	 * 
	 * @param chatData
	 *            発言
	 * @param remoteAddress
	 *            クライアント側IPアドレス
	 */
	public void write(final PacketChatData chatData, String remoteAddress) {
		initializeIfNotInitialized();

		chatData.remoteAddress = remoteAddress;
		chatData.date = System.currentTimeMillis();
		synchronized (writeLock) {
			chatData.resId = data.isEmpty() ? 1 : (data.lastEntry().getValue().resId + 1);
			data.put(chatData.resId, chatData);
			chatDataListWebSockets.send(PacketChatDataList.fromChatData(chatData));
		}
		data.remove(chatData.resId - Constant.CHAT_MAX_RESPONSES - 1);

		ThreadPool.get().execute(new Runnable() {
			public void run() {
				try {
					database.addChatLog(chatData);
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * 発言を読み込む
	 * 
	 * @param nextResponseId
	 *            次に読み込むレスポンス番号
	 * @return　発言リスト
	 */
	public PacketChatDataList read(int nextResponseId) {
		initializeIfNotInitialized();

		return PacketChatDataList.fromChatData(data.tailMap(nextResponseId).values());
	}

	/**
	 * チャット受信用WebSocketインスタンスを返す
	 * 
	 * @return WebSocketインスタンス
	 */
	public WebSocket getChatDataListWebSocket() {
		return chatDataListWebSockets.newWebSocket();
	}
}
